package com.mrogrape.util;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
import java.util.ResourceBundle;

/**
 * 一个基于文件的持久化属性集，可以用来保存程序的设置。
 * 
 * <p>
 * 此类封装了java.util.Properties类，并实现PersistenceSet&lt;String,String&gt;接口,
 * 可以实现字符串类型属性的持久化。
 * 
 * <p>
 * 只需要在构造Settins对象时指定文件位置,Settings类便可以方便的提取其中的数据并储存
 * 至对象中供调用。可以指定某个Settings对象为默认属性集。这样未能在原有的属性集列表中
 * 搜索到属性键，则会搜索第二个属性列表。也可以指定一个ResourceBundle资源为最小属性
 * 集，在这种情况下，搜索顺序将是：当前属性集，默认属性集，ResourceBundle资源集。
 * 
 * <p>
 * 虽然可以通过指定默认属性集的方式实现更高层次的搜索，但是不推荐。
 * 
 * <p>
 * 可以读取的属性集文件采用类Properties属性集文件的格式。它是一个纯文本文档
 * （扩展名随意）。里面的格式应该类似于如下
 * 
 * <p>
 * <code>#Wed Feb 16 19:38:16 CST 2011
 * <br>key=value
 * <br>......
 * </code>
 * 
 * <p>
 * 其中，key为键，即属性名，value为属性值。以#打头的某行则代表注释，其中也可以包含
 * 任意数量的空行。
 * 
 * <p><b>注意</b>
 * <ul>
 * <li>因为采用Reader流读取文件，所以属性集文件中可以出现中文。但不推荐
 * <li>save()方法采用 ISO-8859-1 字符编码保存配置，所以无法在该字符集下识别的字符
 * 都将使用 Unicode 转义序列储存
 * <li>在调用save()方法前，所有的属性都不会被储存
 * <li>因为涉及到文件操作，请确保程序有足够的权限
 * </ul>
 * 
 * @author  Ice Spring
 * @version 1.4.20110920
 * @see     java.util.Properties
 */
public class Settings implements PersistenceSet<String,String>{


	/**
	 * 这个方法可以快速创建一个默认的，数据储存在用户主目录下的Settings对象。
	 * 
	 * <p>
	 * 此方法会在用户的主目录下生成一个名字和参数类的包名相同并且以"."开头的目录，然后
	 * 在里面生成一个名字和参数类名相同的"properties"类型文件用来储存数据。
	 * 
	 * <p>
	 * 此方法通过系统属性"user.home"来获取用户主目录。
	 * 
	 * @param c 一个类，一般是某个Application的主类。
	 * @return 属性集对象。
	 */
	public static Settings createDefaultUserSettings(Class<?> c){

		String packageName = c.getPackage().getName();
		if(StringUtils.isEmpty(packageName)){
			packageName = ".default";
		}

		File dir = new File(System.getProperty("user.home") + File.separator
				+ "." + packageName);
		File file = new File(dir,c.getSimpleName()+".properties");

		return new Settings(file);
	}

	/**
	 * 
	 * @param dirName
	 * @param fileName
	 * @return
	 */
	public static Settings createDefaultUserSettings(String dirName,String fileName){

		String packageName = dirName;
		if(StringUtils.isEmpty(packageName)){
			packageName = ".default";
		}

		File dir = new File(System.getProperty("user.home") + File.separator
				+ packageName);
		File file = new File(dir,fileName);

		return new Settings(file);
	}


	/**
	 * 构建默认的Settings对象。
	 */
	public Settings(){
		this(null,null,null);
	}

	/**
	 * 根据属性集文件对象创建Settings对象。
	 * 
	 * @param file 配置文件对象。
	 */
	public Settings(File file){
		this(null,null,file);
	}

	/**
	 * 根据配置文件路径创建Settings对象，该对象拥有一个默认的属性表。
	 * 
	 * @param defaultSet 默认属性表。
	 * @param file 属性集文件对象。
	 */
	public Settings(Settings defaultSet ,File file){
		this(defaultSet,null,file);
	}

	/**
	 * 根据配置文件对象创建Settings对象，该对象拥有默认的资源包。
	 * 
	 * @param rb 资源包。
	 * @param file 属性集文件对象。
	 * @since 1.3
	 */
	public Settings(ResourceBundle rb , File file){
		this(null,rb,file);
	}

	/**
	 * 根据配置文件对象创建settings对象，并且指定默认设置和默认资源。
	 * 
	 * @param default1 默认设置。
	 * @param default2 默认资源包。
	 * @param file 配置文件对象。
	 * @since 1.3
	 */
	public Settings(Settings default1,ResourceBundle default2,File file){
		this.defaultSet = default1;
		this.resources = default2;
		this.file = file;
		this.init();

	}

	/**
	 * 根据键获取值。
	 */
	public String getAttribute(String key) {
		String value = null;

		value = this.pro.getProperty(key);

		if(value == null && this.defaultSet!=null){
			value = this.defaultSet.getAttribute(key);
		}

		if(value == null && this.resources!=null){
			value = this.resources.getString(key);
		}

		return value;
	}

	/**
	 * 获取当前配置的默认配置集。
	 * @return the defaultSet
	 */
	public Settings getDefaultSet() {
		return this.defaultSet;
	}

	/**
	 * 获取当前配置的属性集文件对象。
	 * 
	 * @return the file
	 */
	public File getFile() {
		return this.file;
	}

	/**
	 * 获取当前配置的默认资源包。
	 * 
	 * @return the resources
	 */
	public ResourceBundle getResourcesBundle() {
		return this.resources;
	}

	/**
	 * 更改当前对象的保存文件并重新加载文件中的属性。
	 * 
	 * <p>
	 * <b>注意：</b>此方法已同步。
	 * 
	 * <p>
	 * <b>警告：</b>此方法会清空原有的所有属性。但不会清除默认属性集中的属性。
	 * 
	 * @param file 需要加载的文件。
	 */
	public void reloadSettings(File file){
		this.file = file;
		this.loadProperties();
	}

	/**
	 * 保存属性集到文件。
	 */
	public boolean save(){
		return this.save(null);
	}

	/**
	 * 保存属性集到文件，同时添加或者更新注释。
	 * 
	 * @param comments 注释。
	 * @return 是否储存成功。
	 */
	public synchronized boolean save(String comments){

		if(this.file == null){
			throw new NullPointerException("File not exists");
		}

		try {
			File parent = this.file.getParentFile();
			if(!parent.exists()){

				boolean b = parent.mkdirs();
				if(b == false){
					return false;
				}
			}
			FileOutputStream fos = new FileOutputStream(this.file);
			this.pro.store(fos, comments);
			fos.close();
			return true;
		} catch (FileNotFoundException e) {
			return false;
		} catch (IOException e) {
			System.out.println(e.getMessage());
			return false;
		}
	}

	@Override
	public String setAttribute(String key, String value) {
		Object o = this.pro.setProperty(key, value);
		if(o!=null){
			return o.toString();
		}
		return null;
	}

	/**
	 * 设置默认属性集
	 * @param defaultSet 默认属性集对象
	 * @since 1.3
	 */
	public void setDefaultSettings(Settings defaultSet){
		this.defaultSet = defaultSet;
	}

	/**
	 * 把一个资源包(ResourceBundle)对象作为默认属性集表列
	 * @param resources 用作默认属性集表列的资源包
	 * @since 1.3
	 */
	public void setResourceBundle(ResourceBundle resources){
		this.resources = resources;
	}

	private void init(){
		this.pro = new Properties();
		if(this.file!=null){
			this.loadProperties();
		}
	}

	/**
	 * 加载属性集文件
	 */
	private synchronized void loadProperties(){
		this.pro.clear();

		if(!this.file.exists()){
			return;
		}

		try {
			FileReader fr = new FileReader(this.file);
			this.pro.load(fr);
			fr.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private File           file;
	private Properties     pro;
	private Settings       defaultSet;
	private ResourceBundle resources;

}
